managing state in next.js with redux - reactjs

I am new to next.js and I am building a small application to test how to manage state which I will implement in a larger app. I followed the documentation to set up my store... below is my reducer
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const combinedReducer = combineReducers({
reducers,
})
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
}
if (state.loading.loading) nextState.loading.loading = state.loading.loading // preserve count value on client side navigation
return nextState
} else {
return combinedReducer(state, action)
}
}
const initStore = () => {
return createStore(reducer, bindMiddleware([thunkMiddleware]))
}
export const wrapper = createWrapper(initStore)
and my -app.js folder
import { wrapper } from '../Store/store'
const WrappedApp = ({ Component, pageProps }) => {
return (
<Fragment>
<HeadComponent />
<Component {...pageProps} />
</Fragment>
)
}
export default wrapper.withRedux(WrappedApp)
in my index.js file which I will also share, I managed the state locally which is fine since it is a small app. I want to learn how to manage this with redux so that I can replicate this in a larger project I will be working on. thanks
const App = () => {
const [posts, setPosts] = useState([])
const [loading, setLoading] = useState(false)
const [userInput, setUserInput] = useState("")
useEffect(() => {
setLoading(true)
axios.get("https://jsonplaceholder.typicode.com/posts")
.then(response => {
setLoading(false)
setPosts(response.data)
})
.catch(error => {
console.log(error)
})
}, [])
const handleChange = (e) => {
setUserInput({ ...userInput, [e.target.name]: e.target.value })
}
const handleSubmit = (e) => {
setLoading(true)
e.preventDefault()
const payload = {
firstName: userInput.firstName,
lastName: userInput.lastName,
password: userInput.password
}
axios.post("https://jsonplaceholder.typicode.com/users", payload)
.then(response => {
console.log(response);
setLoading(false)
})
.catch(error => {
console.log(error)
})
}
return (
<div>
<div style={{ textAlign: "center" }}>
<form onSubmit={handleSubmit}>
<input type="text" name="firstNmae" placeholder="First name" onChange={handleChange} /> <br />
<input type="text" name="lastName" placeholder="Last name" onChange={handleChange} /> <br />
<input type="password" name="password" placeholder="Password" onChange={handleChange} /> <br />
<input type="reset" />
<button>Submit</button>
</form>
</div>
{loading ? <div className={style.spinnerPositioning}><Loader /></div> : <div className={styles.container}>
{posts.map(item => {
return (
<ul key={item.id}>
<li>{item.title}</li>
</ul>
)
})}
</div>}
</div>
)
}
export default App;

If you have to implement Redux into Next.JS this with-redux might solved your problem or Next.JS Faq there is an article there for redux.
You can ask the community of next.js on this link

Related

Refresh a Component from sibling Component in React

I have 2 sibling components namely <AddTask/> and <TaskList/> which are children of <Home/> component. Currently, when I add a new task in my ToDo App, it will be added but I need to refresh the page in order for it to display the new task. How can I refresh the <TaskList/> component immediately after I click the Add button in <AddTask/> component?
Here is my <AddTask/> Component
const AddTask = () => {
const [task, setTask] = useState("");
const [isPending, setIsPending] = useState(false);
const handleClick = (e)=> {
e.preventDefault();
setIsPending(true);
const todo = {task};
fetch('http://localhost:8000/tasks', {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify(todo)
})
.then(()=>{
setIsPending(false);
})
};
return (
<form className="new-task" onSubmit={handleClick}>
<input className="input"
type="text"
required
value={task}
onChange= { (e)=> setTask(e.target.value) }
/>
<button className="add-task">Add</button>
</form>
);
}
export default AddTask;
This is the <Home/> Component
import TaskList from "./TaskList";
import useFetch from "./useFetch";
const Home = () => {
const { data: task, isPending, error} = useFetch('http://localhost:8000/tasks');
return (
<div className="home">
<AddTask />
{ error && <div>Failed to fetch data.</div> }
{ isPending && <div>Loading...</div> }
{ task && <TaskList task={task} /> }
</div>
);
}
export default Home;
In Home component, you need a tasks state so you can update that state in AddTask component
Home
import TaskList from "./TaskList";
import useFetch from "./useFetch";
import { useState, useEffect } from 'react'
const Home = () => {
const [tasks, setTasks] = useState(null);
const { data: task, isPending, error} = useFetch('http://localhost:8000/tasks');
useEffect(() => {
if (task) setTasks(task)
}, [task])
return (
<div className="home">
<AddTask setTasks={setTasks} />
{ error && <div>Failed to fetch data.</div> }
{ isPending && <div>Loading...</div> }
{ tasks && <TaskList task={tasks} /> }
</div>
);
}
export default Home;
AddTask
const AddTask = ({ setTasks }) => {
const [task, setTask] = useState("");
const [isPending, setIsPending] = useState(false);
const handleClick = (e)=> {
e.preventDefault();
setIsPending(true);
const todo = {task};
fetch('http://localhost:8000/tasks', {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify(todo)
})
.then(()=>{
setIsPending(false);
setTasks(prev => ([...prev, task]))
})
};
return (
<form className="new-task" onSubmit={handleClick}>
<input className="input"
type="text"
required
value={task}
onChange= { (e)=> setTask(e.target.value) }
/>
<button className="add-task">Add</button>
</form>
);
}
export default AddTask;

how to test and mock react function/component

this is my test for user registration using jest + react testing library, the problem is that the test update the db.
therefore at the second run the test fails (beacuse the first run registered the user)
so my question is anyone know how can I mock this function?
I will be grateful for any help I could get. thanks in advance
the test
test('signup should dispatch signupAction', async () => {
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
initialState = {
authReducer: { isAuthenticatedData: false },
};
const store = mockStore(initialState);
render(
<Provider store={store}>
<Router>
<UserSignup />
</Router>
</Provider>
);
const nameTextbox = screen.getByPlaceholderText('Name*');
const emailTextbox = screen.getByPlaceholderText('Email*');
const passwordTextbox = screen.getByPlaceholderText('Password*');
const confirmTextbox = screen.getByPlaceholderText('Confirm Password*');
const signupButton = screen.getByRole('button', { name: 'Register' });
userEvent.type(nameTextbox, 'newtestuser');
userEvent.type(emailTextbox, 'newtestuser#gmail.com');
userEvent.type(passwordTextbox, 'testuser123');
userEvent.type(confirmTextbox, 'testuser123');
userEvent.click(signupButton);
await waitFor(() => expect(store.getActions()[0].type).toBe('SIGNUP_SUCCESS'));
});
sign up component
const userSignup = () => {
const dispatch = useDispatch();
const isAuthenticatedData = useSelector((state) => state.authReducer.isAuthenticatedData);
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
re_password: '',
});
const [accountCreated, setAccountCreated] = useState(false);
const { name, email, password, re_password } = formData;
const onChange = (e) => setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = (e) => {
e.preventDefault();
if (password === re_password) {
try {
dispatch(
signupAction({
name,
email,
password,
re_password,
})
);
setAccountCreated(true);
} catch {
window.scrollTo(0, 0);
}
}
};
if (isAuthenticatedData) return <Redirect to='/' />;
if (accountCreated) return <Redirect to='/login' />;
return (
<div data-testid='userSignup'>
<h1>Sign Up</h1>
<p>Create your Account</p>
<form onSubmit={(e) => onSubmit(e)}>
<div>
<input
type='text'
placeholder='Name*'
name='name'
value={name}
onChange={(e) => onChange(e)}
required
/>
</div>
<div>
<input
type='email'
placeholder='Email*'
name='email'
value={email}
onChange={(e) => onChange(e)}
required
/>
</div>
<div>
<input
type='password'
placeholder='Password*'
name='password'
value={password}
onChange={(e) => onChange(e)}
minLength='6'
required
/>
</div>
<div>
<input
type='password'
placeholder='Confirm Password*'
name='re_password'
value={re_password}
onChange={(e) => onChange(e)}
minLength='6'
required
/>
</div>
<button type='submit'>Register</button>
</form>
<p>
Already have an account? <Link to='/login'>Sign In</Link>
</p>
</div>
);
};
export default connect()(userSignup);
sign up action
export const signupAction =
({ name, email, password, re_password }) =>
async (dispatch) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const body = JSON.stringify({
name,
email,
password,
re_password,
});
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/djoser/users/`, body, config);
dispatch({ type: SIGNUP_SUCCESS, payload: res.data });
} catch (err) {
dispatch({ type: SIGNUP_FAIL });
}
};
Assuming you are writing unit tests (which is probably where you should start), then you are looking for a concept called "mocking." The idea is that your React unit tests should only test your React code. Your React unit tests should not depend on a database or even an API. That introduces all sorts of challenges, as you have discovered.
Basically how mocking frameworks work is you configure them with some fake data. Then when you run the tests, your code uses that fake data instead of calling the API.
I see you are using axios to call your API. I suggest you check out axios-mock-adapter to help you mock those axios calls.
I managed to solve this problem here is the test for those who needs it
import '#testing-library/jest-dom/extend-expect';
import { render, screen } from '#testing-library/react';
import { Provider } from 'react-redux';
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import UserSignup from '../../../components/users/UserSignup';
import configureStore from 'redux-mock-store';
import { signupAction } from '../../../redux/actions/auth';
import thunk from 'redux-thunk';
import userEvent from '#testing-library/user-event';
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
let initialState = {
authReducer: {},
};
const store = mockStore(initialState);
jest.mock('../../../redux/actions/auth', () => ({ signupAction: jest.fn() }));
test('Redux - signup should dispatch signupAction', () => {
render(
<Provider store={store}>
<Router>
<UserSignup />
</Router>
</Provider>
);
initialState = {
authReducer: { isAuthenticatedData: false },
};
const store = mockStore(initialState);
render(
<Provider store={store}>
<Router>
<UserSignup />
</Router>
</Provider>
);
const nameTextbox = screen.getByPlaceholderText('Name*');
const emailTextbox = screen.getByPlaceholderText('Email*');
const passwordTextbox = screen.getByPlaceholderText('Password*');
const confirmTextbox = screen.getByPlaceholderText('Confirm Password*');
const signupButton = screen.getByRole('button', { name: 'Register' });
const nameValue = 'testuser';
const emailValue = 'testuser#gmail.com';
const passwordValue = 'testuser123';
const rePasswordValue = 'testuser123';
userEvent.type(nameTextbox, nameValue);
userEvent.type(emailTextbox, emailValue);
userEvent.type(passwordTextbox, passwordValue);
userEvent.type(confirmTextbox, rePasswordValue);
userEvent.click(signupButton);
const timesActionDispatched = signupAction.mock.calls.length;
expect(timesActionDispatched).toBe(1);
expect(signupAction.mock.calls[0][0].name).toEqual(nameValue);
expect(signupAction.mock.calls[0][0].email).toEqual(emailValue);
expect(signupAction.mock.calls[0][0].password).toEqual(passwordValue);
expect(signupAction.mock.calls[0][0].re_password).toEqual(rePasswordValue);
});

How to submit a form with react and redux?

I am trying to submit a POST form with a simple html form and I also use redux with a userAction which allows to store the user and the token returned by the express js API, but the submission does not work, when I submit the form, nothing happens I can't even get into the fetch function of the action.
import '../styles/ConnexionModal.css';
import { userLogin } from '../redux/userActions';
import { useState } from 'react';
// import { useSelector } from 'react-redux';
const ConnexionModal = ({ showModal, hideModal }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// const message = useSelector(state => state.userReducer.message);
// console.log(message);
const handleChangeEmail = (event) => {
setEmail(event.target.value);
}
const handleChangePassword = (event) => {
setPassword(event.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
userLogin(email, password);
}
return (
showModal && (
<div className="modalBg">
<div className="modalContainer">
<div className="modalHeader">
<h1 className="modalTitle">Connexion</h1>
</div>
<div className="modalBody">
<form method="POST" onSubmit={handleSubmit}>
<div className="formGroup">
<label htmlFor="email" className="info">Email</label>
<input className="field" name="email" id="email" type="email" value={email} onChange={handleChangeEmail} autoFocus required />
</div>
<div className="formGroup">
<label htmlFor="password" className="info">Mot de passe</label>
<input className="field" name="password" id="password" type="password" value={password} onChange={handleChangePassword} required />
</div>
<div className="formGroup">
<label htmlFor="connexion" className="submitButton">Se connecter</label>
<input className="field submit" id="connexion" name="submit" type="submit" />
</div>
</form>
</div>
<div className="close">
<button onClick={() => hideModal()} className="closeButton">Fermer</button>
</div>
</div>
</div>
)
)
}
export default ConnexionModal;
export const LOGIN = "LOGIN";
export const ERROR = "ERROR";
export const userLogin = (email, password) => {
return async dispatch => {
console.log('test');
fetch('http://192.168.1.36:4000/api/users/login', {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
password: password
})
})
.then((res) => res.json())
.then(async (res) => {
if(!res.error) {
localStorage.setItem('auth', res.token);
dispatch({ type: LOGIN, user: res.user, token: res.token });
} else {
dispatch({ type: ERROR, message: res.error });
}
})
}
}
The console.log ('test') does not display anything which means I am not even accessing the userLogin function in my component.
I assume you are also using redux-thunk.
You should always pass the action inside your container to a redux dispatch function. E.g.
import '../styles/ConnexionModal.css';
import { userLogin } from '../redux/userActions';
import { useState } from 'react';
import { useDispatch } from 'react-redux';
const ConnexionModal = ({ showModal, hideModal }) => {
const dispatch = useDispatch();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleChangeEmail = (event) => {
setEmail(event.target.value);
}
const handleChangePassword = (event) => {
setPassword(event.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
dispatch(userLogin(email, password));
}
...

Update state after axios POST request instead of page refresh

I have a form with input fields 'title' and 'body', they are being added to MongoDB after submitting the form, but the new post item shows up only after I refresh the page (that of course is not how it should work), but I want to know what I am doing wrong and how to fix the handleSumbit function?
Here is my code:
import { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";
function App() {
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const [posts, setPosts] = useState([]);
const url = "http://localhost:8005/";
useEffect(() => {
const fetchPosts = async () => {
const response = await axios(`${url}posts`);
setPosts(response.data);
};
fetchPosts();
}, []);
const handleTitleChange = (event) => {
setTitle(event.target.value);
};
const handleBodyChange = (event) => {
setBody(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
axios.post(`${url}posts`, { title: title, body: body })
.then((res) => {
console.log(res.data);
});
alert("Post added!");
setTitle("");
setBody("");
};
console.log(posts);
return (
<div className="App">
{posts.map((post) => (
<div className="post" key={post.id}>
<h4>{post.title}</h4>
<p>{post.body}</p>
</div>
))}
<form onSubmit={handleSubmit}>
<label>
Mew post:
<input
type="text"
name="title"
placeholder="Add title"
onChange={handleTitleChange}
/>
<input
type="text"
name="body"
placeholder="Add body"
onChange={handleBodyChange}
/>
</label>
<button type="submit">Add</button>
</form>
</div>
);
}
export default App;
The problem is that you didn't update the posts state after you sent the axios post request. Edit the below code block:
const handleSubmit = (event) => {
event.preventDefault();
axios.post(`${url}posts`, { title: title, body: body })
.then((res) => {
console.log(res.data);
// save the new post to posts
setPosts([...posts, res.data])
});
alert("Post added!");
setTitle("");
setBody("");
};

How to redirect to a another page in reactjs when switch case statement is executed?

I want to know how to make a redirection after a case statement. I have been making this code, but after the case statement, nothing happens. I review on the web, but nothing seems to work.When i submit the validated form, it doesn't redirect or refreshes.
Code
import { Redirect } from 'react-router-dom'
import React, { Component } from 'react'
const initState = {}
const adReducer = (state = initState, action) => {
switch (action.type) {
case 'CREATE_AD_SUCCESS':
alert('create ad success');
return <Redirect to='/' /> ;
case 'CREATE_AD_ERROR':
alert('create ad error');
return state;
default:
return state;
}
};
export default adReducer;
adAction.js code
export const createAd = (ad) => {
return (dispatch, getState, {getFirebase,getFirestore}) => {
// make async call to database
const firestore = getFirestore();
const profile = getState().firebase.profile;
const authorId = getState().firebase.auth.uid;
firestore.collection('ads').add({
...ad,
authorFirstName: profile.firstName,
authorLastName: profile.lastName,
authorId: authorId,
createdAt: new Date()
}).then(() => {
dispatch({ type: 'CREATE_AD_SUCCESS' });
}).catch(err => {
dispatch({ type: 'CREATE_AD_ERROR' }, err);
});
}
};
Create ad code :
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createAd } from '../../store/actions/adActions'
import { Redirect } from 'react-router-dom'
import firebase from "firebase";
import FileUploader from "react-firebase-file-uploader";
class CreateAd extends Component {
state = {
title: '',
content: '',
avatar: "",
isUploading: false,
progress: 0,
avatarURL: "",
contactno:""
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
this.props.createAd(this.state);
}
handleUploadStart = () => this.setState({ isUploading: true, progress: 0 });
handleProgress = progress => this.setState({ progress });
handleUploadError = error => {
this.setState({ isUploading: false });
console.error(error);
};
handleUploadSuccess = filename => {
this.setState({ avatar: filename, progress: 100, isUploading: false });
firebase
.storage()
.ref("images")
.child(filename)
.getDownloadURL()
.then(url => this.setState({ avatarURL: url }));
};
render() {
const { auth } = this.props;
if (!auth.uid) return <Redirect to='/signin' />
return (
<div className="container">
<form className="white" onSubmit={this.handleSubmit}>
<h5 className="grey-text text-darken-3">Create a New Ad</h5>
<div className="input-field">
<input type="text" id='title' onChange={this.handleChange} />
<label htmlFor="title">Ad Title</label>
</div>
<div className="input-field">
<textarea id="content" className="materialize-textarea" onChange={this.handleChange}></textarea>
<label htmlFor="content">AdContent</label>
</div>
<div className="input-field">
<input type="text" id='contactno' onChange={this.handleChange} />
<label htmlFor="title">Contact Number</label>
</div>
{ this.state.progress==100? <div class="col-md-4">
<img class="responsive-img" src={this.state.avatarURL}></img>
</div>:""}
<br/>
<label style={{backgroundColor: 'steelblue', color: 'white', padding: 10, borderRadius: 4, pointer: 'cursor'}}>
Upload a photo
{/* {this.state.isUploading && <p>Progress: {this.state.progress}</p>}
{this.state.avatarURL && <img src={this.state.avatarURL} />} */}
<FileUploader
hidden
accept="image/*"
storageRef={firebase.storage().ref('images')}
onUploadStart={this.handleUploadStart}
onUploadError={this.handleUploadError}
onUploadSuccess={this.handleUploadSuccess}
onProgress={this.handleProgress}
/>
</label>
<div className="input-field">
<button className="btn pink lighten-1">Create</button>
</div>
</form>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
auth: state.firebase.auth
}
}
const mapDispatchToProps = dispatch => {
return {
createAd: (ad) => dispatch(createAd(ad))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CreateAd)
These are my codes.
You should use return window.location.replace("/") instead of return <Redirect to="/" />.
"React Router Redirect" redirects from A to B, for example <Redirect from="/about" to="/" />

Resources