14

Redwood, a new framework

 1 year ago
source link: https://blog.openreplay.com/redwood-a-new-framework
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Back

Redwood, a new framework

July 7th, 2022 · 5 min read
hero.png

According to its documentation,

“Redwood is a full-stack web development framework designed to help you grow from side project to startup.”

Redwood features an end-to-end development workflow that weaves together the best part of React for developing pages and components, GraphQL as the query language for data, Prisma as ORM for accessing data, TypeScript for better programming, Jest for testing, and Storybook to aid with component development. It provides a full-stack development process in JAMStack.

Redwood is obsessed with developer experience and eliminating as much boilerplate as possible where existing libraries elegantly solve these problems, and where we don’t write our solution. The result has a Javascript development experience one can fall in love with!

Features

Let’s look at Redwood’s main basic features that make it look attractive as a full-fledged framework.

  • Project Structure: Two folders are generated as API and Web when creating a Redwood project. API folder contains the GraphQL back end of your project, which runs on any server. The Web folder contains React front-end code; Redwood uses React for the front end, and several features are built around Routing, Components, Forms, and Cells, including unique formatting structures.

  • Distinctive Command-Line: RedwoodJS CLI allows you to generate any file you need in the directory you specify. It includes two entry points, rw and rwt, which stand for developing an application and contributing to the frameworks, respectively. Also, you can use the CLI for migration and scaffolding purposes.

  • Routing: Redwood Router is a customized version of React Router. It allows developers to track Routes with their corresponding pages by listing all the Routes in a single Routing file. The Named Routes feature allows you to add a reference name to each Route as you prefer. There are three main prop types in React Router name (used to call the route component from the link component), path (used to match the URL path to the route), and page (a prop that indicates the relevant component that needs to be rendered when the path is matched).

1// web/src/Routes.js
2import { Router, Route } from ‘@redwoodjs/router’
3const Routes = () => {
4 return (
5 <Router>
6 <Route path=”/” page={HomePage} name=”home” />
7 <Route path=”/users/{type}” page={UsersPage} name=”users” /> </Router>
10// web/src/components/Admin/Admin.js
11import { Link, routes } from ‘@redwoodjs/router’
12const Admin = () => {
13 return (
14 <h1><Link to={routes.home()}>My CRM</Link></h1>
15 <Link to={routes.users({type: “admin”})}>View Admins</Link>
  • Data Fetch with Cells: RedwoodJS cells are considered higher-level components that can handle data for you. If you are using RedwoodJS cells, you probably need to write the query then Redwood will do the rest for you. The data fetching mechanism is quite different from the typical traditional system. Redwood provides an asynchronous tool to fetch data where the front end and back end process their task separately, which makes the front end load without waiting for the data to be fetched. Let’s look at Redwood cells; they contain four states named Loading, Empty, Error, and Success, which are automatically rendered depending on the state of your cell.
1// web/src/components/UsersCell/UsersCell.js
2export const QUERY = gql`
3 query USERS {
4 users {
6 name
10export const Loading = () => <div>Loading users...</div>
11export const Empty = () => <div>No users yet!</div>
12export const Failure = ({ message }) => <div>Error: {message}</div>
13export const Success = ({ users }) => {
14 return (
15 <ul>
16 { users.map(user => (
17 <li>{user.id} | {user.name}</li>
18 ))}
19 </ul>
  • Integration with regular HTML: Redwood has identified the issue of React Forms and its complexities compared to a typical HTML Form, and has provided a solution which is a helper method as a wrapper to React Forms, around react-hook-form. With this method, it’s easier to validate client & server-side where you need to put the required styles to the imported input fields as follows:
1import { Form, Label, TextAreaField, FieldError, Submit } from “@redwoodjs/web”
2export const Comment = () => {
3 const onSubmit = (data) => {
4 console.info(`Submitted: ${data}`)
6 return (
7 <Form onSubmit={onSubmit}> <Label name=”comment” errorStyle={{ color: “red” }} />
8 <TextAreaField
9 name=”comment”
10 errorStyle={{ borderColor: “red” }}
11 validation={{ required: true }}
12 />
13 <FieldError name=”comment” errorStyle={{ color: “red” }} />
14 <Submit>Send</Submit>
15 </Form>

Open Source Session Replay

OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.

replayer.png

Start enjoying your debugging experience - start using OpenReplay for free.

How Redwood works

In a typical setting, Redwood applications are split into two parts: a front end and a back end. This is represented as two JS/TS projects within a single monorepo. The front-end project is called the web side, and the back-end project is called the API side. The web side will end up running in the user’s browser, while code on the API side will run on the server.

Note: Redwood refers to ‘front end’ as ‘web’. This is because Redwood conceives of a world where you may have other sides like ‘mobiles’, ‘desktop’, ‘cli’, etc. But for the sake of this article, we’ll be referring to it as the front end.

The front end is built with React. Redwood’s router makes it simple to map URL paths to React ‘Page’ components. Pages may contain a ‘Layout’ component to wrap content and ‘Cells’ and regular React components. The Cells allow you to declaratively manage the lifecycle of a component that fetches and displays data. Other Redwood utility components make it insignificant to integrate forms and various basic needs.

The back end is an implementation of GraphQL API. Your business logic is organized into ‘services’ that represent their internal API and can be called both from external GraphQL requests and other internal services. Redwood can automatically connect your internal services with Apollo, reducing the amount of boilerplate you have to write.

Before we move on to building our Redwood project, let’s take a look at some of the basic commands of RedwoodJS CLI:

  • yarn rw g page: generates a page component
  • yarn rw prisma migrate dev: Runs migrations on our application
  • yarn rw g sdl: generates a GraphQL schema definition file
  • yarn rw g service: generates the service files
  • yarn rw g cell: generates cell component
  • yarn rw dev: runs Redwood dev server on http://localhost:8910/.

Building an app

We need to illustrate how to use RedwoodJS to build an app. The Todo app will be able to take notes and save those notes on the JSON server. Note: Credit to RedwoodJS for the example.

First steps

To create the project, all we have to do is run the following commands:

1yarn create redwood-app todo-app
2cd todo-app
3yarn rw dev

1

Let’s take a glance at our folder structure:

1todo-app
3 ├── api
4 │ ├── db
5 │ │ ├── schema.prisma
6 │ │ └── seed.js
7 │ └── src
8 │ ├── functions
9 │ │ └── graphql.js
10 │ ├── graphql
11 │ ├── lib
12 │ │ └── db.js
13 │ └── services
14 └── web
15 ├── public
16 │ ├── README.md
17 │ ├── favicon.png
18 │ └── robots.txt
19 └── src
20 ├── Routes.js
21 ├── components
22 ├── index.css
23 ├── index.html
24 ├── App.js
25 ├── layouts
26 └── pages
27 ├── FatalErrorPage
28 │ └── FatalErrorPage.js
29 └── NotFoundPage
30 └── NotFoundPage.js

Defining data models

Now we have to add our Todo model to the schema.prisma file.

1model Todo {
2 id Int @id @default(autoincrement())
3 body String
4 status String @default("off")

Note: Run yarn rw prisma migrate, which will prompt for the name of the migrations; just type added_todo and hit the enter button. Redwood will create and run the migrations.

We’ll create services that will be used to carry out CRUD operations on our todo table in the db:

1yarn rw g service todo
2yarn rw g sdl

This command will create the files:

  • api/src/graphql/todos.sdl.js: contains the GraphQL schema definitions of our GraphQL endpoints.
  • api/src/services/todos/todos.js: our service file contains all query and mutation resolver functions.

Creating the page and components

Our app will have just a page, i.e., the HomePage. The homepage will show the to-dos in the database and the input field.

1yarn rw g page home/ todos

This command creates the to-do page. The main file is located at web/src/pages/HomePage/HomePage.js. The page will map to the URL path ”/” home. We’ll also create a TodoListCell: yarn rw g cell list todo. It will generate files in the components folder. The main file is located at web/src/components/TodoListCell/TodoListcell.js.

We modify the code to this:

1import styled from 'styled-components'
2import TodoItem from 'src/components/TodoItem'
3import { useMutation } from '@redwoodjs/web'
4export const QUERY = gql`
5 query TODOS {
6 todos {
8 body
9 status
13const UPDATE_TODO_STATUS = gql`
14 mutation TodoListCell_CheckTodo($id: Int!, $status: String!) {
15 updateTodoStatus(id: $id, status: $status) {
17 __typename
18 status
22export const Loading = () => <div>Loading...</div>
23export const Empty = () => <div></div>
24export const Failure = () => <div>Oh no</div>
25export const Success = ({ todos }) => {
26 const [updateTodoStatus] = useMutation(UPDATE_TODO_STATUS)
27 const handleCheckClick = (id, status) => {
28 updateTodoStatus({
29 variables: { id, status },
30 optimisticResponse: {
31 __typename: 'Mutation',
32 updateTodoStatus: { __typename: 'Todo', id, status: 'loading' },
36 const list = todos.map((todo) => (
37 <TodoItem key={todo.id} {...todo} onClickCheck={handleCheckClick} />
39 return <SC.List>{list}</SC.List>
41export const beforeQuery = (props) => ({
42 variables: props,
44const SC = {}
45SC.List = styled.ul`
46 padding: 0;

We’ll generate AddTodoControl component, which will have an input form with a placeholder and a submit button to submit our todos. Now, let’s scaffold the AddTodoControl component:

1yarn rw g component AddTodoControl

It creates a AddTodocontrol component located at web/src/components/AddTodoControl/AddTodoControl.js and our code should look like this:

1import styled from 'styled-components'
2import { useState } from 'react'
3import Check from 'src/components/Check'
4const AddTodoControl = ({ submitTodo }) => {
5 const [todoText, setTodoText] = useState('')
6 const handleSubmit = (event) => {
7 submitTodo(todoText)
8 setTodoText('')
9 event.preventDefault()
11 const handleChange = (event) => {
12 setTodoText(event.target.value)
14 return (
15 <SC.Form onSubmit={handleSubmit}>
16 <Check type="plus" />
17 <SC.Body>
18 <SC.Input
19 type="text"
20 value={todoText}
21 placeholder="Memorize the dictionary"
22 onChange={handleChange}
23 />
24 <SC.Button type="submit" value="Add Item" />
25 </SC.Body>
26 </SC.Form>
29const SC = {}
30SC.Form = styled.form`
31 display: flex;
32 align-items: center;
34SC.Body = styled.div`
35 border-top: 1px solid #efefef;
36 border-bottom: 1px solid #efefef;
37 width: 100%;
39SC.Input = styled.input`
40 border: none;
41 font-size: 18px;
42 font-family: 'Inconsolata', monospace;
43 padding: 10px 0;
44 width: 75%;
46 ::placeholder {
47 color: #9e9595;
50SC.Button = styled.input`
51 float: right;
52 margin-top: 5px;
53 border-radius: 6px;
54 background-color: #f75d52;
55 padding: 5px 15px;
56 color: white;
57 border: 0;
58 font-size: 18px;
59 font-family: 'Inconsolata', monospace;
60 :hover {
61 background-color: black;
62 cursor: pointer;
65export default AddTodoControl

We’ll create our TodoItem with the command yarn rw g component TodoItem and this component consists of our style attributes for each todo body, so we’re going to implement things like padding, borders, and lines with the code below:

1import styled from 'styled-components'
2import Check from 'src/components/Check'
3const TodoItem = ({ id, body, status, onClickCheck }) => {
4 const handleCheck = () => {
5 const newStatus = status === 'off' ? 'on' : 'off'
6 onClickCheck(id, newStatus)
8 return (
9 <SC.Item>
10 <SC.Target onClick={handleCheck}>
11 <Check type={status} />
12 </SC.Target>
13 <SC.Body>{status === 'on' ? <s>{body}</s> : body}</SC.Body>
14 </SC.Item>
17const SC = {}
18SC.Item = styled.li`
19 display: flex;
20 align-items: center;
21 list-style: none;
23SC.Target = styled.div`
24 cursor: pointer;
26SC.Body = styled.div`
27 list-style: none;
28 font-size: 18px;
29 border-top: 1px solid #efefef;
30 padding: 10px 0;
31 width: 100%;
33export default TodoItem

The Check component consists of our icons like on, off, plus icons. To create the check component, run yarn rw g component. Input the following code in our Check component:

1import styled from 'styled-components'
2import IconOn from './on.svg'
3import IconOff from './off.svg'
4import IconPlus from './plus.svg'
5import IconLoading from './loading.svg'
6const map = {
7 on: <IconOn />,
8 off: <IconOff />,
9 plus: <IconPlus />,
10 loading: <IconLoading />,
12const Check = ({ type }) => {
13 return <SC.Icon>{map[type]}</SC.Icon>
15const SC = {}
16SC.Icon = styled.div`
17 margin-right: 15px;
19export default Check

We need an AddTodo component to add new todos.

1import { useMutation } from '@redwoodjs/web'
2import AddTodoControl from 'src/components/AddTodoControl'
3import { QUERY as TODOS } from 'src/components/TodoListCell'
5const CREATE_TODO = gql`
6 mutation AddTodo_CreateTodo($body: String!) {
7 createTodo(body: $body) {
9 __typename
10 body
11 status
15const AddTodo = () => {
16 const [createTodo] = useMutation(CREATE_TODO, {
18 update: (cache, { data: { createTodo } }) => {
19 const { todos } = cache.readQuery({ query: TODOS })
20 cache.writeQuery({
21 query: TODOS,
22 data: { todos: todos.concat([createTodo]) },
26 const submitTodo = (body) => {
27 createTodo({
28 variables: { body },
29 optimisticResponse: {
30 __typename: 'Mutation',
31 createTodo: { __typename: 'Todo', id: 0, body, status: 'loading' },
35 return <AddTodoControl submitTodo={submitTodo} />
37export default AddTodo

This is our final project.

Redwood final project

Conclusion

There are many other reasons to get attracted to RedwoodJS.

  • Opinionated defaults for formatting, file organization, Webpack, Babel, and more
  • Generators for pages, layouts, cells, SDL, services, etc.
  • Forms with easy client-and/or server-side validation and error handling
  • Database and Data migrations

All these and many more make RedwoodJS unique to the core.

Resources

newsletter

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK