Goal:
*Get the data of of variable Cars to the 'this.state.data' when you have retrieved the data from API.
*Display data from 'this.state.data' and not using the variable Cars.
Problem:
I do not know how to do it and is is it possible to do it when you have applied refactoring SOLID?
Info:
I'm newbie in React JS.
Stackblitz:
https://stackblitz.com/edit/react-v39jre?
App.js
import React from 'react';
import './style.css';
import CarsList from './components/CarsList';
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
data: null
};
}
render() {
return (
<div className="App">
<CarsList />
</div>
);
}
}
export default App;
CarsList.jsx
import React, { useState, useEffect } from 'react';
this.state = {
name: 'React',
data: null
};
const CarsList = () => {
const [cars, setCars] = useState([]);
useEffect(() => {
const fetchCars = async () => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
setCars(await response.json());
};
fetchCars();
}, []);
return (
<div>
{cars.map((car, index) => (
<li key={index}>
[{++index}]{car.id} - {car.name}$
</li>
))}
</div>
);
};
export default CarsList;
After getting the response in the child component you should do a callback function which can be passed as prop from parent to child. Using the function you can pass the data from child to parent and update the parent state.
App.js
import { useState } from "react";
import CarsList from "./CarsList";
import "./styles.css";
export default function App() {
const [state, setState] = useState([]);
const handleUpdateParentState = (data) => {
setState(data);
};
console.log("state in parent", state);
return (
<div>
<CarsList updateParentState={handleUpdateParentState} />
</div>
);
}
CarsList.js
import React, { useState, useEffect } from "react";
const CarsList = (props) => {
const [cars, setCars] = useState([]);
useEffect(() => {
const fetchCars = async () => {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
const data = await response.json();
setCars(data);
props?.updateParentState(data);
} catch (error) {
console.log(error);
}
};
fetchCars();
}, []);
return (
<ul>
{cars?.map((car, index) => (
<li key={index}>
[{++index}]{car.id} - {car.name}$
</li>
))}
</ul>
);
};
export default CarsList;
Codesandbox
Data can be shared using props but from parent component to child component only. We cannot pass child component state to parent component through props.
Though we can create a function at parent level and pass it to child component as props so we can execute there.
In your case, you have to create a function in App component and pass it on carList component as props. In carList component you do not have to create the cars state. After fetching the cars from API just call the function you passed from App component
App.js
import React from 'react';
import './style.css';
import CarsList from './components/CarsList';
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
data: null
};
}
function setCarList(cars) {
this.setState({
date: cars
});
}
render() {
return (
<div className="App">
<CarsList setCars={setCarList}/>
</div>
);
}
}
export default App;
CarList.js
import React, {useEffect } from 'react';
this.state = {
name: 'React',
data: null
};
const CarsList = (props) => {
useEffect(() => {
const fetchCars = async () => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
this.props.setCars(await response.json());
};
fetchCars();
}, []);
return (
<div>
{cars.map((car, index) => (
<li key={index}>
[{++index}]{car.id} - {car.name}$
</li>
))}
</div>
);
};
export default CarsList;
It doesn't make much sense for each CarList component to load data if you're going to have loads of them and they're going to share information with each other. You should load all your data in your App component using an array of API fetch calls and then use Promise.all to extract and parse the data, and then add it to the state. That state can be then shared with all your Carlist components.
Here's a React component:
const {Component} = React;
const json = '["BMW", "Clio", "Merc", "Fiat"]';
// Simulates an API call
function mockFetch() {
return new Promise((res, rej) => {
setTimeout(() => res(json), 1000);
});
}
class App extends Component {
constructor() {
super();
this.state = { cars: [] };
}
componentDidMount() {
// Have an array fetches (you would supply each one a
// different API endpoint in your code)
const arr = [mockFetch(), mockFetch(), mockFetch()];
// Grab the json, `map` over it and parse it
Promise.all(arr).then(data => {
const cars = data.map(arr => JSON.parse(arr));
// Then set the new state
this.setState(prev => ({ ...prev, cars }));
});
}
// You can now send the data to your small functional
// carlist components
render() {
const { cars } = this.state;
if (!cars.length) return <div />;
return (
<div>
<Carlist cars={cars[0]} />
<Carlist cars={cars[1]} />
<Carlist cars={cars[2]} />
</div>
)
}
};
function Carlist({ cars }) {
return (
<ul>{cars.map(car => <div>{car}</div>)}</ul>
);
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
And here's equivalent written as a functional component with hooks:
const {useState, useEffect} = React;
const json = '["BMW", "Clio", "Merc", "Fiat"]';
function mockFetch() {
return new Promise((res, rej) => {
setTimeout(() => res(json), 1000);
});
}
function App() {
const [cars, setCars] = useState([]);
// This works in the same way as the previous example
// except we're not setting `this.state` we're setting the
// state called `cars` that we set up with `useState`.
useEffect(() => {
function getData() {
const arr = [mockFetch(), mockFetch(), mockFetch()];
Promise.all(arr).then(data => {
const cars = data.map(arr => JSON.parse(arr));
setCars(cars);
});
}
getData();
}, []);
if (!cars.length) return <div />;
return (
<div>
<Carlist cars={cars[0]} />
<Carlist cars={cars[1]} />
<Carlist cars={cars[2]} />
</div>
);
};
function Carlist({ cars }) {
return (
<ul>{cars.map(car => <div>{car}</div>)}</ul>
);
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Related
I Can't render my events. Its showing this error -
"Cannot update a component (App) while rendering a different component (EventList). To locate the bad setState() call inside EventList, follow the stack trace as described in https://reactjs.org/link/setstate-in-render"
Here is EventList Component code -
import { useEffect, useState } from "react";
import EventList from "../../event-list";
import EventForm from "../event-form";
const EventAction = ({
getEventsByClockID,
addEvent,
updateEvent,
clockID,
deleteEvent,
deleteEventsByClockID,
}) => {
const [isCreate, setIsCreate] = useState(false);
const [isToggle, setIsToggle] = useState(false);
const [eventState, setEventState] = useState(null)
const handleCreate = () => {
setIsCreate(!isCreate);
}
useEffect(() => {
setEventState(getEventsByClockID(clockID, true));
}, [isToggle])
const handleToggle = () => {
setIsToggle(!isToggle);
}
return (
<div>
<div>
<button onClick={handleCreate}>Create Event</button>
<button onClick={handleToggle}>Toggle Events</button>
</div>
{isCreate && (
<>
<h3>Create Event</h3>
<EventForm
clockID={clockID}
handleEvent={addEvent}
/>
</>
)}
{isToggle && (
<>
<h3>Events of this clock</h3>
<EventList
clockID={clockID}
eventState={eventState}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</>
)}
</div>
)
}
export default EventAction;
Here is my App Component Code -
import ClockList from "./components/clock-list";
import LocalClock from "./components/local-clock";
import useApp from "./hooks/useApp";
import { localClockInitState } from "./initialStates/clockInitState";
const App = () => {
const {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
} = useApp(localClockInitState);
return (
<div>
<LocalClock
clock={localClock}
updateClock={updateLocalClock}
createClock={createClock}
/>
<ClockList
clocks={clocks}
localClock={localClock.date}
updateClock={updateClock}
deleteClock={deleteClock}
getEventsByClockID={getEventsByClockID}
addEvent={addEvent}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</div>
)
}
export default App;
and Here is my useApp hook -
import { useState } from "react";
import deepClone from "../utils/deepClone";
import generateID from "../utils/generateId";
import useEvents from "./useEvents";
const getID = generateID('clock');
const useApp = (initValue) => {
const [localClock, setLocalClock] = useState(deepClone(initValue));
const [clocks, setClocks] = useState([]);
const {
// events,
// getEvents,
getEventsByClockID,
addEvent,
deleteEvent,
deleteEventsByClockID,
updateEvent,
} = useEvents();
const updateLocalClock = (data) => {
setLocalClock({
...localClock,
...data,
})
}
const createClock = (clock) => {
clock.id = getID.next().value;
setClocks((prev) => ([
...prev, clock
]))
}
const updateClock = (updatedClock) => {
setClocks(clocks.map(clock => {
if(clock.id === updatedClock.id) return updatedClock;
return clock;
}));
}
const deleteClock = (id) => {
setClocks(clocks.filter(clock => clock.id !== id));
}
return {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
}
}
export default useApp;
I want to show all events incorporated with each individual clock.
REACT.js:
Let say I have a home page with a search bar, and the search bar is a separate component file i'm calling.
The search bar file contains the useState, set to whatever the user selects. How do I pull that state from the search bar and give it to the original home page that
SearchBar is called in?
The SearchBar Code might look something like this..
import React, { useEffect, useState } from 'react'
import {DropdownButton, Dropdown} from 'react-bootstrap';
import axios from 'axios';
const StateSearch = () =>{
const [states, setStates] = useState([])
const [ stateChoice, setStateChoice] = useState("")
useEffect (()=>{
getStates();
},[])
const getStates = async () => {
let response = await axios.get('/states')
setStates(response.data)
}
const populateDropdown = () => {
return states.map((s)=>{
return (
<Dropdown.Item as="button" value={s.name}>{s.name}</Dropdown.Item>
)
})
}
const handleSubmit = (value) => {
setStateChoice(value);
}
return (
<div>
<DropdownButton
onClick={(e) => handleSubmit(e.target.value)}
id="state-dropdown-menu"
title="States"
>
{populateDropdown()}
</DropdownButton>
</div>
)
}
export default StateSearch;
and the home page looks like this
import React, { useContext, useState } from 'react'
import RenderJson from '../components/RenderJson';
import StateSearch from '../components/StateSearch';
import { AuthContext } from '../providers/AuthProvider';
const Home = () => {
const [stateChoice, setStateChoice] = useState('')
const auth = useContext(AuthContext)
console.log(stateChoice)
return(
<div>
<h1>Welcome!</h1>
<h2> Hey there! Glad to see you. Please login to save a route to your prefered locations, or use the finder below to search for your State</h2>
<StateSearch stateChoice={stateChoice} />
</div>
)
};
export default Home;
As you can see, these are two separate files, how do i send the selection the user makes on the search bar as props to the original home page? (or send the state, either one)
You just need to pass one callback into your child.
Homepage
<StateSearch stateChoice={stateChoice} sendSearchResult={value => {
// Your Selected value
}} />
Search bar
const StateSearch = ({ sendSearchResult }) => {
..... // Remaining Code
const handleSubmit = (value) => {
setStateChoice(value);
sendSearchResult(value);
}
You can lift the state up with function you pass via props.
const Home = () => {
const getChoice = (choice) => {
console.log(choice);
}
return <StateSearch stateChoice={stateChoice} giveChoice={getChoice} />
}
const StateSearch = (props) => {
const handleSubmit = (value) => {
props.giveChoice(value);
}
// Remaining code ...
}
Actually there is no need to have stateChoice state in StateSearch component if you are just sending the value up.
Hello and welcome to StackOverflow. I'd recommend using the below structure for an autocomplete search bar. There should be a stateless autocomplete UI component. It should be wrapped into a container that handles the search logic. And finally, pass the value to its parent when the user selects one.
// import { useState, useEffect } from 'react' --> with babel import
const { useState, useEffect } = React // --> with inline script tag
// Autocomplete.jsx
const Autocomplete = ({ onSearch, searchValue, onSelect, suggestionList }) => {
return (
<div>
<input
placeholder="Search!"
value={searchValue}
onChange={({target: { value }}) => onSearch(value)}
/>
<select
value="DEFAULT"
disabled={!suggestionList.length}
onChange={({target: {value}}) => onSelect(value)}
>
<option value="DEFAULT" disabled>Select!</option>
{suggestionList.map(({ id, value }) => (
<option key={id} value={value}>{value}</option>
))}
</select>
</div>
)
}
// SearchBarContainer.jsx
const SearchBarContainer = ({ onSelect }) => {
const [searchValue, setSearchValue] = useState('')
const [suggestionList, setSuggestionList] = useState([])
useEffect(() => {
if (searchValue) {
// some async logic that fetches suggestions based on the search value
setSuggestionList([
{ id: 1, value: `${searchValue} foo` },
{ id: 2, value: `${searchValue} bar` },
])
}
}, [searchValue, setSuggestionList])
return (
<Autocomplete
onSearch={setSearchValue}
searchValue={searchValue}
onSelect={onSelect}
suggestionList={suggestionList}
/>
)
}
// Home.jsx
const Home = ({ children }) => {
const [result, setResult] = useState('')
return (
<div>
<SearchBarContainer onSelect={setResult} />
result: {result}
</div>
)
}
ReactDOM.render(<Home />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Just pass a setState to component
parent component:
const [state, setState] = useState({
selectedItem: ''
})
<StateSearch state={state} setState={setState} />
change parent state from child component:
const StateSearch = ({ state, setState }) => {
const handleStateChange = (args) => setState({…state, selectedItem:args})
return (...
<button onClick={() => handleStateChange("myItem")}/>
...)
}
I have the following problem: I use a fatch API to get all the products I have registered in my database (mongodb), then I store the result in a slice called products-slice which has an array as its initial state empty. Until then everything is in order. As I need information the time the homepage is loaded, I use the useEffect hook to fetch the products I have registered. Then I pass this array as props to a component, and make a map. The problem is that when the component loads, the information is not local.
código do backend
module.exports.fetchProduct = async (req, res) => {
try {
const products = await Product.find({});
if (products) {
res.json(products);
}
} catch (error) {
console.log(error);
}
};
productsActions.js
export const fetchProducts = () => {
return async (dispatch) => {
try {
const response = await fetch("http://localhost:xxxx/xxxxxx");
const data = await response.json();
let loadedProducts = [];
for (const key in data) {
loadedProducts.push({
id: data[key]._id,
productName: data[key].productName,
price: data[key].price,
imageUrl: data[key].imageUrl,
});
}
dispatch(setProducts(loadedProducts));
} catch (error) {
console.log(error);
}
};
};
home.jsx
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Container } from "react-bootstrap";
import {fetchProducts} from '../../store/actions/productsActions';
import Hero from "../hero/Hero";
import Footer from "../footer/Footer";
import DisplayProductsList from "../displayProduct/DisplayProductsList";
export default function Home() {
const productsInfo = useSelector((state) => state.products.products);
console.log(productsInfo);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
return (
<>
<Hero />
<DisplayProductsList products={productsInfo} />
<Container fluid>
<Footer></Footer>
</Container>
</>
);
}
product-slice.js
const initialState = {
products: [],
};
const productSlice = createSlice({
name: "product",
initialState,
reducers: {
setProducts(state, action) {
state.products.push(action.payload);
},
},
});
export const { setProducts } = productSlice.actions;
export default productSlice.reducer;
component where I'm mapping
export default function DisplayProductsList(props) {
console.log(props);
return (
props.products.map((product) => (
<DisplayProducts
key={product.id}
imageUrl={product.imageUrl}
name={product.productName}
price={product.price}
/>
))
);
}
console.log output in the above component
enter image description here
How to pass the {requests} prop to the RequestRow component after executing the setRequests? My understanding is that the requests get initialized as undefined in the beginning and before being set with the asynchronously called object, it gets passed to the RequestRow component as undefined, and the error occurs.
import React, { useState, useEffect } from 'react';
import 'semantic-ui-css/semantic.min.css';
import Layout from '../../../components/Layout';
import { Button } from 'semantic-ui-react';
import { Link } from '../../../routes';
import Campaign from '../../../blockchain/campaign';
import { Table } from 'semantic-ui-react';
import RequestRow from '../../../components/RequestRow';
const RequestsIndex = ({ address }) => {
const { Header, Row, HeaderCell, Body } = Table;
const campaign = Campaign(address);
const [requestCount, setRequestCount] = useState();
const [requests, setRequests] = useState([]);
const getRequests = async () => {
const count = await campaign.methods.getRequestsCount().call();
setRequestCount(count);
};
let r;
const req = async () => {
r = await Promise.all(
Array(parseInt(requestCount))
.fill()
.map((_element, index) => {
return campaign.methods.requests(index).call();
})
);
setRequests(r);
};
useEffect(() => {
getRequests();
if (requestCount) {
req();
}
}, [requestCount]);
return (
<Layout>
<h3>Requests List.</h3>
<Link route={`/campaigns/${address}/requests/new`}>
<a>
<Button primary>Add Request</Button>
</a>
</Link>
<Table>
<Header>
<Row>
<HeaderCell>ID</HeaderCell>
<HeaderCell>Description</HeaderCell>
<HeaderCell>Amount</HeaderCell>
<HeaderCell>Recipient</HeaderCell>
<HeaderCell>Approval Count</HeaderCell>
<HeaderCell>Approve</HeaderCell>
<HeaderCell>Finalize</HeaderCell>
</Row>
</Header>
<Body>
<Row>
<RequestRow requests={requests}></RequestRow>
</Row>
</Body>
</Table>
</Layout>
);
};
export async function getServerSideProps(context) {
const address = context.query.address;
return {
props: { address },
};
}
export default RequestsIndex;
The RequestRow component is shown below. It takes in the {requests} props, which unfortunately is undefined.
const RequestRow = ({ requests }) => {
return requests.map((request, index) => {
return (
<>
<div>Request!!!</div>
</>
);
});
};
export default RequestRow;
The snapshot of the error is shown below:
I think React is trying to render your component before your promises resolve. If that's the case, all you need to do is set a default value (an empty array in your case) for your requests.
const [requests, setRequests] = useState([]);
May the force be with you.
I have a header component which listens for loggedInUser data from Redux store. I want to unit test for redux prop values. Like i have mocked a redux store for initial values and want to test for those values in props of the connected component.
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faUser, faShoppingCart } from '#fortawesome/free-solid-svg-icons';
import { NavLink, useHistory } from 'react-router-dom';
import Cart from './../Cart/Cart.component';
import { signOutStart } from './../../redux/user/user.actions';
import './Header.styles.scss';
export const Header = ({ noOfItemsInCart, loggedInUser, signOut }) => {
const [isUserDropDownVisible, setUserDropDownVisibility] = useState(false);
const [isCartDropDownVisible, setCartDropDownVisibility] = useState(false);
const history = useHistory();
console.log(loggedInUser);
return (
<header className = 'header' id = 'header'>
<NavLink to = '/'><p className = 'title'>Kart</p></NavLink>
{loggedInUser ? (
<div className = 'header__options' id = 'header__options'>
<div className = 'cart__options'>
<FontAwesomeIcon
icon={faShoppingCart}
onClick = {() => {
setUserDropDownVisibility(false);
setCartDropDownVisibility(prevState => {return !prevState})}
} />
<span><sup>{noOfItemsInCart}</sup></span>
{isCartDropDownVisible ? (
<div className="dropdown">
<Cart />
</div>
) : null}
</div>
<div className = 'user__options'>
<FontAwesomeIcon
icon={faUser}
onClick = {() => {
setCartDropDownVisibility(false);
setUserDropDownVisibility(prevState => {return !prevState})}
} />
{isUserDropDownVisible ? (
<div className="dropdown" onClick = {() => setUserDropDownVisibility(false)}>
<NavLink to = '/orders'>My Orders</NavLink>
<span onClick = { async () => {
await signOut();
history.push('/auth');
} }>Logout</span>
</div>
) : null}
</div>
</div>
) : null}
</header>
)
}
const mapStateToProps = state => {
return {
loggedInUser: state.user.loggedInUser,
noOfItemsInCart: state.cart.noOfItemsInCart
}
}
const mapDispatchToProps = dispatch => {
return {
signOut: () => dispatch(signOutStart())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
I had implemented an unit test as follows, by using a shallow render of component and tried accessing the props using .props()
import React from 'react';
import { shallow } from 'enzyme';
import configureMockStore from 'redux-mock-store';
import Header from './Header.component';
const mockStore = configureMockStore();
describe('<Header />', () => {
let wrapper, store;
beforeEach(() => {
const initialState = {
user: {
loggedInUser: 'user1',
error: null
},
cart: {
noOfCartItemsInCart: 0
}
}
store = mockStore(initialState);
wrapper = shallow(
<Header store = {store} />
)
});
it('should have valid props', () => {
expect(wrapper.props().loggedInUser).toBe('user1');
})
})
I am getting prop values a undefined or null values. How to test for prop values to an redux connected component?
Have you tried this from the docs?
wrapper.instance().props