I've been driving myself insane trying to figure out why this React component won't render. After checking in a local environment it works fine, but I need this to work on CodePen.io to turn it in for a FreeCodeCamp Challenge. Any help would be greatly appreciated.
I've tried putting in an 'unpkg' source, adding it to the html in script tags (the React packages) and using the integrated npm tool in the codepen.io js tab in the settings. None of this worked and I think the code should work.
import React from 'http://esm.sh/react#18.2.0'
import ReactDOM from 'http://esm.sh/react-dom#18.2.0'
import * as redux from "http://cdn.skypack.dev/redux#4.2.0";
import reduxThunk from "http://cdn.skypack.dev/redux-thunk#2.4.2";
import { Provider, connect } from "http://cdn.skypack.dev/react-redux#8.0.5";
// Initial State
const initialState = {
quote: "Let's go",
author: "Bob J. Baker",
backgroundColor: "#f5f5f5"
};
// Actions: Types
const CHANGE_ALL = "CHANGE_ALL";
// Action: Functions
const changeAll = () => {
return async (dispatch) => {
try {
dispatch({ type: CHANGE_ALL });
const response = await fetch("https://quotes.rest/quote/random");
const data = await response.json();
const color = getRandomColor();
dispatch({
type: CHANGE_ALL,
quote: data.contents.quotes[0].quote,
author: data.contents.quotes[0].author,
backgroundColor: color
});
} catch (error) {
console.log(error);
}
};
};
const getRandomColor = () => {
const randomColor = "#" + Math.random().toString(16).substr(-6);
return randomColor;
};
// Reducer
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_ALL:
return {
...state,
quoteAndAuthor: { quote: action.quote, author: action.author },
backgroundColor: action.backgroundColor
};
default:
return state;
}
};
// Store
const store = redux.createStore(rootReducer, redux.applyMiddleware(reduxThunk));
//**********Component://**********
class RandomQuoteComponent extends React.Component {
// state & props
constructor(props) {
super(props);
}
componentDidMount() {
// running the get all action
this.props.changeAll();
}
render() {
return (
<div
id="main-wrapper"
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%",
width: "100%",
backgroundColor: this.props.backgroundColor
}}
>
<div id="quote-box">
<h2 id="text">{this.props.quote}</h2>
<h6 id="author">{this.props.author}</h6>
<button id="new-quote">New Quote</button>
<a href="#" id="tweet-quote">
<i className="fab fa-twitter"></i>
</a>
</div>
</div>
);
}
}
////**********END OF COMPONENT//**********
// Mapping for state and dispatch actions to props
const mapStateToProps = (state) => {
return {
backgroundColor: state.backgroundColor,
quote: state.quoteAndAuthor.quote,
author: state.quoteAndAuthor.author
};
};
const mapDispatchToProps = (dispatch) => {
return {
changeAll: () => dispatch(changeAll())
};
};
const ConnectedRandomQuoteComponent = connect(
mapStateToProps,
mapDispatchToProps
)(RandomQuoteComponent);
ReactDOM.render(
<Provider store={store}>
<ConnectedRandomQuoteComponent />
</Provider>,
document.getElementById("root")
);
Related
I have a problem that children component does not update hidden status when project is selected, it should then display all the the tasks included in selected projects. How ever when getTasks is done and it updates hidden state to false and it passes state to children component props but children component never reintialize select component and remains hidden. What I need to change to make my selectbox class RtSelect to display hidden state changes?
My master component:
import React, { useState, useEffect, useRef } from 'react';
import RtSelect from './RtSelect';
import api, { route } from "#forge/api";
function Projects() {
const projRef = useRef();
const taskRef = useRef();
const [projects, setProjects] = useState(undefined)
const [tasks, setTasks] = useState(undefined)
const [projectid, setProjectid] = useState(undefined)
const [taskid, setTaskid] = useState(undefined)
const [hidden, setHidden] = useState(true)
//haetaan atlasiansita projectit array
useEffect(() => {
let loadedProject = true;
// declare the async data fetching function
const fetchProjects = async () => {
// get the data from the api
const response = await api.asUser().requestJira(route`/rest/api/3/project`, {
headers: {
'Accept': 'application/json'
}
});
const data = await response.json();
//Mapataa hausta tarvittavat tiedot
const result = data.map(function (item) {
console.log('test');
return [
{
label: item.name,
value: item.id,
avatar: item.avatarUrls['16x16']
}
]
})
// set state with the result if `isSubscribed` is true
if (loadedProject) {
setProjects(result);
}
}
//asetetaan state selectbox muutokselle
// call the function
fetchProjects()
// make sure to catch any error
.catch(console.error);;
// cancel any future `setData`
return () => loadedProject = false;
}, [param])
const getTasks = async (p) => {
// get the data from the api
const response = await api.asUser().requestJira(route`/rest/api/3/issuetype/project?projectId={p}`, {
headers: {
'Accept': 'application/json'
}
});
const data = await response.json();
//Mapataa hausta tarvittavat tiedot
const result = data.map(function (item) {
console.log('test');
return [
{
value: item.id,
label: item.description,
avatar: item.iconUrl
}
]
})
setTasks(result)
setHidden(false)
}
useEffect(() => {
projRef.current.addEventListener("onChange", (e) => {
setProjectid(e.target.value)
console.log("Project select boxin arvo on: " + e.target.value);
getTasks(projectid)
});
});
useEffect(() => {
taskRef.current.addEventListener("onChange", (e) => {
setTaskid(e.target.value)
console.log("Select task boxin arvo on: " + e.target.value);
});
});
return (
<div>
<div className='projects'>
<RtSelect info="Choose project:" options={projects} hidden={false} ref={projRef} />
</div>
<div className='tasks'>
<RtSelect info="Choose Task:" options={tasks} hidden={hidden} ref={taskRef} />
</div>
</div>
);
}
export default Projects
Here is my RtSelect class code:
import React from "react";
import Select from "react-select";
class RtSelect extends React.Component {
state = {
info: this.props.info,
options: this.props.options,
hidden: this.props.hidden,
menuIsOpen: '',
menuWidth: "",
IsCalculatingWidth: ''
};
constructor(props) {
super(props);
this.selectRef = props.ref
this.onMenuOpen = this.onMenuOpen.bind(this);
this.setData = this.setData.bind(this);
}
componentDidMount() {
if (!this.state.menuWidth && !this.state.isCalculatingWidth) {
setTimeout(() => {
this.setState({IsCalculatingWidth: true});
// setIsOpen doesn't trigger onOpenMenu, so calling internal method
this.selectRef.current.select.openMenu();
this.setState({menuIsOpen: true});
}, 1);
}
}
onMenuOpen() {
if (!this.state.menuWidth && this.state.IsCalculatingWidth) {
setTimeout(() => {
const width = this.selectRef.current.select.menuListRef.getBoundingClientRect()
.width;
this.setState({menuWidth: width});
this.setState({IsCalculatingWidth: false});
// setting isMenuOpen to undefined and closing menu
this.selectRef.current.select.onMenuClose();
this.setState({menuIsOpen: undefined});
}, 1);
}
}
styles = {
menu: (css) => ({
...css,
width: "auto",
...(this.state.IsCalculatingWidth && { height: 0, visibility: "hidden" })
}),
control: (css) => ({ ...css, display: "inline-flex " }),
valueContainer: (css) => ({
...css,
...(this.state.menuWidth && { width: this.state.menuWidth })
})
};
setData (props) {
if (props.info) {
this.setState({
info: props.info
})
}
if (props.options) {
this.setState({
options: props.options
})
}
if (props.hidden) {
this.setState({
hidden: props.hidden
})
}
}
render () {
return (
<div style={{ display: "flex" }}>
<div style={{ margin: "8px" }}>{this.state.info}</div>
<div style={{minWidth: "200px"}}>
<Select
ref={this.selectRef}
onMenuOpen={this.onMenuOpen}
options={this.state.options}
menuIsOpen={this.state.menuIsOpen}
styles={this.styles}
isDisabled={this.state.hidden}
formatOptionLabel={(options) => (
<div className="select-option" style={{ display: "flex", menuWidth: "200px"}}>
<div style={{ display: "inline", verticalAlign: "center" }}>
<img src={options.avatar} width="30px" alt="Avatar" />
</div>
<div style={{ display: "inline", marginLeft: "10px" }}>
<span>{options.label}</span>
</div>
</div>
)}
/>
</div>
</div>
);
}
}
export default RtSelect;
Ok I found from other examples that I can use the ref to acces child method so here is they way to update component:
useEffect(() => {
projRef.current.addEventListener("onChange", (e) => {
setProjectid(e.target.value)
console.log("Project select boxin arvo on: " + e.target.value);
getTasks(projectid)
//Using RtSelect taskRef to locate children component method to update component
taskRef.current.setData({hidden: false})
});
});
I'm building a Next.js app which allows users to create and view multiple Kanban boards. There are a couple of different ways that a user can view their different boards:
On the Home page of the app, users see a list of boards that they can click on.
The main navigation menu has a list of boards users can click on.
Both use Next.js Link components.
Clicking the links loads the following dynamic page: src/pages/board/[boardId].js The [boardId].js page fetches the board data using getServerSideProps(). An effect fires on route changes, which updates the redux store. Finally, the Board component uses a useSelector() hook to pull the data out of Redux and render it.
The problem I'm experiencing is that if I click back and forth between different boards, I see a brief flash of the previous board's data before the current board data loads. I am hoping someone can suggest a change I could make to my approach to alleviate this issue.
Source code:
// src/pages/board/[boardId].js
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import Board from 'Components/Board/Board'
import { useRouter } from 'next/router'
import { hydrateTasks } from 'Redux/Reducers/TaskSlice'
import { unstable_getServerSession } from 'next-auth/next'
import { authOptions } from 'pages/api/auth/[...nextauth]'
import prisma from 'Utilities/PrismaClient'
const BoardPage = ({ board, tasks }) => {
const router = useRouter()
const dispatch = useDispatch()
useEffect(() => {
dispatch(hydrateTasks({ board, tasks }))
}, [router])
return (
<Board />
)
}
export async function getServerSideProps ({ query, req, res }) {
const session = await unstable_getServerSession(req, res, authOptions)
if (!session) {
return {
redirect: {
destination: '/signin',
permanent: false,
},
}
}
const { boardId } = query
const boardQuery = prisma.board.findUnique({
where: {
id: boardId
},
select: {
name: true,
description: true,
id: true,
TO_DO: true,
IN_PROGRESS: true,
DONE: true
}
})
const taskQuery = prisma.task.findMany({
where: {
board: boardId
},
select: {
id: true,
title: true,
description: true,
status: true,
board: true
}
})
try {
const [board, tasks] = await prisma.$transaction([boardQuery, taskQuery])
return { props: { board, tasks } }
} catch (error) {
console.log(error)
return { props: { board: {}, tasks: [] } }
}
}
export default BoardPage
// src/Components/Board/Board.js
import { useEffect } from 'react'
import { useStyletron } from 'baseui'
import Column from 'Components/Column/Column'
import ErrorBoundary from 'Components/ErrorBoundary/ErrorBoundary'
import useExpiringBoolean from 'Hooks/useExpiringBoolean'
import { DragDropContext } from 'react-beautiful-dnd'
import Confetti from 'react-confetti'
import { useDispatch, useSelector } from 'react-redux'
import useWindowSize from 'react-use/lib/useWindowSize'
import { moveTask } from 'Redux/Reducers/TaskSlice'
import { handleDragEnd } from './BoardUtilities'
import { StyledBoardMain } from './style'
const Board = () => {
const [css, theme] = useStyletron()
const dispatch = useDispatch()
useEffect(() => {
document.querySelector('body').style.background = theme.colors.backgroundPrimary
}, [theme])
// get data from Redux
const { boardDescription, boardName, columnOrder, columns, tasks } = useSelector(state => state?.task)
// set up a boolean and a trigger to control "done"" animation
const { boolean: showDone, useTrigger: doneUseTrigger } = useExpiringBoolean({ defaultState: false })
const doneTrigger = doneUseTrigger({ duration: 4000 })
// get width and height for confetti animation
const { width, height } = useWindowSize()
// curry the drag end handler for the drag and drop UI
const curriedDragEnd = handleDragEnd({ dispatch, action: moveTask, handleOnDone: doneTrigger })
return (
<ErrorBoundary>
<DragDropContext onDragEnd={curriedDragEnd}>
<div className={css({
marginLeft: '46px',
marginTop: '16px',
fontFamily: 'Roboto',
display: 'flex',
alignItems: 'baseline'
})}>
<h1 className={css({ fontSize: '22px', color: theme.colors.primary })}>{boardName}</h1>
{boardDescription &&
<p className={css({ marginLeft: '10px', color: theme.colors.primary })}>{boardDescription}</p>
}
</div>
<StyledBoardMain>
{columnOrder.map(columnKey => {
const column = columns[columnKey]
const tasksArray = column.taskIds.map(taskId => tasks[taskId])
return (
<Column
column={columnKey}
key={`COLUMN_${columnKey}`}
tasks={tasksArray}
title={column.title}
status={column.status}
/>
)
})}
</StyledBoardMain>
</DragDropContext>
{showDone && <Confetti
width={width}
height={height}
/>}
</ErrorBoundary>
)
}
export default Board
// src/pages/index.tsx
import React, {PropsWithChildren} from 'react'
import {useSelector} from "react-redux";
import {authOptions} from 'pages/api/auth/[...nextauth]'
import {unstable_getServerSession} from "next-auth/next"
import CreateBoardModal from 'Components/Modals/CreateBoard/CreateBoard'
import Link from 'next/link'
import {useStyletron} from "baseui";
const Index: React.FC = (props: PropsWithChildren<any>) => {
const {board: boards} = useSelector(state => state)
const [css, theme] = useStyletron()
return boards ? (
<>
<div style={{marginLeft: '46px', fontFamily: 'Roboto', width: '600px'}}>
<h1 className={css({fontSize: '22px'})}>Boards</h1>
{boards.map(({name, description, id}) => (
<Link href="/board/[boardId]" as={`/board/${id}`} key={id}>
<div className={css({
padding: '20px',
marginBottom: '20px',
borderRadius: '6px',
background: theme.colors.postItYellow,
cursor: 'pointer'
})}>
<h2 className={css({fontSize: '20px'})}>
<a className={css({color: theme.colors.primary, width: '100%', display: 'block'})}>
{name}
</a>
</h2>
<p>{description}</p>
</div>
</Link>
))}
</div>
</>
) : (
<>
<h1>Let's get started</h1>
<button>Create a board</button>
</>
)
}
export async function getServerSideProps(context) {
const session = await unstable_getServerSession(context.req, context.res, authOptions)
if (!session) {
return {
redirect: {
destination: '/signin',
permanent: false,
},
}
}
return {props: {session}}
}
export default Index
It looks like there's only ever one board in the redux. You could instead use a namespace so that you don't have to keep swapping different data in and out of the store.
type StoreSlice = {
[boardId: string]: Board;
}
Then the "brief flash" that you will see will either be the previous data for the correct board, or nothing if it has not yet been fetched.
I tried to add a recipe to favourites using Redux with React Native.
If I use the state in the favicon.js this works fine but does not work with Redux.
This is my favicon.js
import React, {useState} from 'react'
import { FontAwesome } from '#expo/vector-icons'
import { View, TouchableOpacity,StyleSheet, Text, Alert } from 'react-native'
import { connect } from 'react-redux'
import { toggleFavId, isFavourite } from '../actions'
const FavIcon = ({recipeid, toggle, isFav, text}) => {
// const [isFavNew, setFavNew] = useState(false)
// const toggleFav = (recipeid) =>{
// setFavNew(!isFavNew)
// console.log(`Entered toggle ${recipeid} isfavnew = ${isFavNew}`)
// }
return (
<View>
<TouchableOpacity style={styles.favItem} onPress={() => {toggle(recipeid)}}>
<FontAwesome name={isFav == true ? 'heart' : 'heart-o'} size={40} color='red' />
<Text> {text}</Text>
</TouchableOpacity>
</View>
)
}
const mapStateToProps = (state) =>({
favids: state.favids,
})
const mapDispatchToProps = (dispatch) => ({
toggle: (recipeid) => dispatch(toggleFavId(recipeid)),
isFav: (recipeid) => dispatch(isFavourite(recipeid)),
})
export default connect(mapStateToProps,mapDispatchToProps)(FavIcon)
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: '#fff',
alignItems: 'flex-start',
justifyContent: 'center',
},
favItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
marginTop:10,
paddingTop:15,
paddingBottom:15,
marginLeft:30,
marginRight:30,
backgroundColor:'#00BCD4',
borderRadius:10,
borderWidth: 1,
borderColor: '#fff',
height: 75,
width: 300,
}
});
This if the reducer....the code enters the TOGGLE switch but the state is not updated with the new values.
import {TOGGLE, IS_FAVOURITE} from '../actions'
export const favids = (state=[], action) => {
const {type, recipeid} = action
const newRecipe = {
recipeid: recipeid,
is_fav: true,
}
switch (type) {
case TOGGLE: {
console.log(`Toggle State = ${JSON.stringify(state)}`)
console.log(`NEW REcipe ${JSON.stringify(newRecipe)} `)
// console.log(`Toggle State after adding = ${JSON.stringify(state)}`)
// return state.map(item=>{
// state.recipeid === recipeid ?
// {...item, newRecipe} : {...item,newRecipe}
// })
return {...state, newRecipe}
}
case IS_FAVOURITE: {
console.log(`Is Favourite state =${JSON.stringify(state)}`)
return state.map(item=>{
item.recipeid === recipeid ?
{...item, is_fav: !item.is_fav} : item
})
}
default:
return state
}
}
I've tried different methods of updating the state (see below) but the state is not updated.
I've commented out the older code which was not working
// return state.map(item=>{
// state.recipeid === recipeid ?
// {...item, newRecipe} : {...item,newRecipe}
// })
return {...state, newRecipe}
Any help would be appreciated.
This is the index.js file from the '../actions' folder
export const TOGGLE = 'TOGGLE'
export const toggleFavId = id => ({
type: 'TOGGLE',
recipeid: id
})
export const IS_FAVOURITE = 'IS_FAVOURITE'
export const isFavourite = id => (
{
type: 'IS_FAVOURITE',
recipeid: id
}
)
export const TOGGLE_FAV = 'TOGGLE_FAV'
export const toggleFav = id => ({
type: 'TOGGLE_FAV',
recipeid: id,
})
this is the VisibleFavicon.js file
import {connect} from 'react-redux'
import Favicon from '../components/favicon'
const mapStateToProps = state =>({
favids: state
})
const mapDispatchToProps = dispatch => ({
toggleFavIds: id => dispatch ({
type: 'TOGGLE',
id
}),
isFavourite : id => dispatch ({
type: 'IS_FAVOURITE',
id
}),
})
export default connect(mapStateToProps, mapDispatchToProps)(Favicon)
Try to get whole state of redux instead of state.favids and wherever you want to use that state you can access it like props.state.favids :
const mapStateToProps = (state) =>({
favids: state,
})
Heres the solution to the issue, this is the code for the reducer.
import { TOGGLE, IS_FAVOURITE } from '../actions'
export const favids = (state = [], action) => {
const { type, recipeid } = action
const newRecipe = {
recipeid,
is_fav: true,
}
switch (type) {
case TOGGLE: {
return state.findIndex(recipe => recipe.recipeid === recipeid) === -1 ?
[...state, newRecipe] :
state.map(recipe =>
recipe.recipeid === recipeid ?
{ ...recipe, is_fav: !recipe.is_fav } : recipe
)
}
default:
return state
}
}
I need to access my current cart state which is just a list of products that have
been added to the cart, so that I check ids in order to calculate quantity for duplicate products. I realize that one of my issues here is that i've initialized itemsInCart with an empty array but i'm not sure what else to do here since, state can't be destructured without it.
cartReducer.js
const itemsInCart = []
export const cartReducer = (state = itemsInCart, action) => {
const { type, payload } = action;
switch (type) {
case "ADD_TO_CART":
return [
...state,
{
imgUrl: payload.imgUrl,
name: payload.name,
price: payload.price,
quantity: payload.quantity
},
];
default:
}
return state;
};
Product.js
Clicking the button dispatches the 'ADD_TO_CART' action, adds new products to our cart in state.
import React from 'react';
import {useDispatch} from 'react-redux';
const Product = ({imgUrl, name, price, id }) => {
const dispatch = useDispatch()
const addToCart = () => {
dispatch({
type: "ADD_TO_CART",
payload: {
imgUrl: imgUrl,
name: name,
price: price,
id: id
}
})
}
return (
<div
key={id}
style={{
textAlign: "center",
display: "flex",
border: "1px solid",
marginBottom: "2rem",
flexDirection: 'column'
}}
>
<img
src={imgUrl}
style={{ height: "5rem", width: "5rem" }}
alt="The product"
/>
<h3>{name}</h3>
<p>${price}.00</p>
{id}
<button onClick={()=>addToCart()}>Add To Cart</button>
</div>
);
}
export default Product;
InCartList.js
Renders list of items in my cart inside CartContainer
import React from "react";
import { useSelector } from "react-redux";
import CartItem from "./CartItem";
const ProductList = () => {
const allState = useSelector((state) => state.cart);
const renderProducts = () => {
return allState.map((product) => {
return (
<CartItem id={product.id} quantity={product.quantity}key={product.id} name={product.name} price={product.price}/>
);
});
};
return <>{renderProducts()}</>;
};
export default ProductList;
You shouldn't place any logic inside reducer (reducer should only pure function)
You can try to get state you want before dispatch action ADD_TO_CART
use getStateToProps function
use store which should be exported when initialized inside App component (I guess)
export const store = configureAppStore(history);
I'm trying to send into my componentsObject in FooScreen any props and to use it into the components, but it not let me use it.
const FooScreen = ({props}) => <Center><Text>{props}</Text></Center>;
const BarScreen = () => <Center><Text>Bar</Text></Center>;
const components = {
Foo: FooScreen({name:'test1'}),
Bar: BarScreen({name:'test2'}),
};
const Center = ({ children }) => (
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>{children}</View>
);
const pages = [
{ screenName: 'Foo', componentName: 'Foo' },
{ screenName: 'Bar', componentName: 'Bar' },
];
i send it as props in Screen and in other screen i try to use it as
class TabBarView extends Component {
constructor(props){
super(props);
this.state = {
tabs: ''
}
}
componentDidMount(){
console.log(this.props)
}
componentWillMount(){
console.log(this.props)
const {pages,components} = this.props
setTimeout(() => {
const screens = {};
pages.forEach(page => {
screens[page.screenName] = { screen: components[page.componentName] };
});
this.setState({ tabs: TabNavigator(screens) });
}, 2000);
}
render() {
if (this.state.tabs) {
return <this.state.tabs />;
}
return <View><Text>Loading...</Text></View>;
}
}
it fail and not let me do that.
later, I want to use in FooScreen as real screen in react and set it into stackNavigator
I get the error
The component for route 'Foo' must be a react component
I suggest the component returns function instead of React element. It's easy to assign a key for each element.
The setState should not be used in componentWillMount, especially when there is a timer to cause side-effect.
For efficiency reason, I tested the code below on web. If you replace div with View and p with Text, this should work in React Native. Don't forget import { Text, View } from 'react-native'
import React, { Component } from 'react';
const FooScreen = props => (
<Center>
<Text>{`[Foo] ${props.name}`}</Text>
</Center>
);
const BarScreen = props => (
<Center>
<Text>{`[Bar] ${props.name}`}</Text>
</Center>
);
const components = {
Foo: (key) => <FooScreen name="test1" key={key} />,
Bar: (key) => <BarScreen name="test2" key={key} />,
};
const Center = props => (
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
{props.children}
</View>
);
const pages = [ 'Foo', 'Bar' ];
export default class TabBardiv extends Component {
state = {
tabs: null,
};
componentDidMount() {
console.log(pages);
setTimeout(() => {
this.setState({ tabs: pages });
}, 2000);
}
render() {
if (!this.state.tabs) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
const screens = pages.map((page, index) => {
const element = components[page];
console.log(element);
return element(index);
});
return screens;
}
}